perf(css): audit :has() selectors and add stylelint guard for Safari#7708
perf(css): audit :has() selectors and add stylelint guard for Safari#7708hectahertz wants to merge 4 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: 5e93384 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
There was a problem hiding this comment.
Pull request overview
Audits and hardens :has() selector usage across the Primer React monorepo to prevent Safari/WebKit performance regressions, adding lint guardrails and documenting safe usage patterns.
Changes:
- Add a Stylelint rule to disallow
:has()by default with a Safari performance warning message. - Add
stylelint-disableaudit comments to existing CSS Module files that already rely on:has(). - Remove redundant
:has([data-color-mode])selectors from@primer/styled-reactBaseStyles and document:has()guidance for contributors.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| stylelint.config.mjs | Disallows :has() via Stylelint rule with error message guidance. |
| packages/styled-react/src/components/BaseStyles.tsx | Removes :has([data-color-mode]) selectors from global BaseStyles styling. |
| packages/react/src/experimental/SelectPanel2/SelectPanel.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/TreeView/TreeView.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Timeline/Timeline.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/SegmentedControl/SegmentedControl.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/PageHeader/PageHeader.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Dialog/Dialog.module.css | Adds file-level Stylelint disable for audited body:has(...) legacy path. |
| packages/react/src/ButtonGroup/ButtonGroup.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Button/ButtonBase.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/Breadcrumbs/Breadcrumbs.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/AvatarStack/AvatarStack.module.css | Extends existing disables to include the new :has() disallow rule. |
| packages/react/src/ActionList/Group.module.css | Adds file-level Stylelint disable for audited :has() usage. |
| packages/react/src/ActionList/ActionList.module.css | Extends existing disables to include the new :has() disallow rule. |
| .github/instructions/css.instructions.md | Documents :has() risks, safe patterns, and the required lint override approach. |
| */ | ||
| /* stylelint-disable-next-line selector-no-qualifying-type */ | ||
| /* stylelint-disable-next-line selector-no-qualifying-type, selector-pseudo-class-disallowed-list -- :has() on body guarded by feature flag negation, audited (github/github-ui#17224) */ | ||
| body:not([data-dialog-scroll-optimized]):has(.Dialog.DisableScroll) { |
There was a problem hiding this comment.
we can/should just ship the flag here that enables the optimization path soon!
I just keep missing the window's where it's unlocked to do that
#7633
There was a problem hiding this comment.
I've added #7633 to my list of “PRs to merge in the next release.” I’ll keep an eye on it and merge it the next time main is unlocked.
b025450 to
b66b0cd
Compare
|
🤖 Formatting issues have been automatically fixed and committed to this PR. |
Closes https://github.com/github/github-ui/issues/17224
In Aug 2025, a
:has()selector on a broadly-present component caused 10-20+ second freezes on Safari due to quadratic style invalidation in WebKit (398 reactions, 86 participants, 446 points on HN). This PR audits all:has()usage in@primer/reactand adds guardrails to prevent a repeat.Audit summary: 46
:has()occurrences across 12 CSS Module files. All are low-risk because CSS Modules generate unique hashed class names, limiting the invalidation blast radius to a small subtree. The one dangerous pattern (body:has(...)in Dialog) was already mitigated with a feature flag guard.Changes:
Stylelint rule (
selector-pseudo-class-disallowed-list: ['has']) blocks new:has()by default. Developers must add astylelint-disablewith justification.File-level
stylelint-disablecomments on all 12 audited files, referencing the tracking issue.Removed redundant
:has()from@primer/styled-reactBaseStyles. Same:has([data-color-mode])pattern already removed from@primer/reactin #7325. The global selectors already handle input color-scheme.:has()guidance in CSS instructions (.github/instructions/css.instructions.md) covering safe vs dangerous patterns.Full audit with per-selector risk assessment posted on the tracking issue.
Changelog
New
:has()pseudo-class by default with Safari perf warningChanged
stylelint-disableaudit comments to 12 existing CSS files with:has()usage:has()guidanceRemoved
:has([data-color-mode])selectors from@primer/styled-reactBaseStyles (redundant with existing global selectors)Rollout strategy
Testing & Reviewing
:has()usage with clear error messagestylelint-disablecommentsnpm run lint:csspasses cleanMerge checklist
cc @mattcosta7